﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Security.Permissions;

using NMemcached.Client.Binary;
using NMemcached.Client.Configuration;
using NMemcached.Client.Text;

namespace NMemcached.Client
{
    /// <summary>
    /// A specialized collection of ServerConnection objects
    /// </summary>
    [Serializable]
    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
    public class ServerConnectionCollection : HashSet<ServerConnection>
    {
        private object _syncRoot;

        /// <summary>
        /// Gets or sets the default conneciton type for new servers added by host name
        /// </summary>
        public ConnectionType DefaultConnectionType { get; set; }
        /// <summary>
        /// Gets or sets the default transport type for new servers added by host name
        /// </summary>
        public TransportType DefaultTransportType { get; set; }

        /// <summary>
        /// Initializes a new instance of the ServerConnectionCollection class
        /// </summary>
        public ServerConnectionCollection()
            : base()
        {
            this.Initialize();
        }

        /// <summary>
        /// Initializes a new instance of the ServerConnectionCollection class
        /// </summary>
        /// <param name="serverNames">A collection of server names to add to the collection using the default port</param>
        public ServerConnectionCollection(IEnumerable<string> serverNames)
            : this(serverNames, 11211)
        {
        }

        /// <summary>
        /// Initializes a new instance of the ServerConnectionCollection class
        /// </summary>
        /// <param name="serverNames">A collection of server names to add to the collection</param>
        /// <param name="port">The port number for each server</param>
        public ServerConnectionCollection(IEnumerable<string> serverNames, int port)
        {
            this.Initialize();
            if (serverNames != null)
            {
                foreach (string server in serverNames)
                {
                    this.Add(server, port);
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the ServerConnectionCollection class
        /// </summary>
        /// <param name="collection">A collection whose elements are copied to the new collection</param>
        public ServerConnectionCollection(IEnumerable<ServerConnection> collection)
            : base(collection)
        {
            this.Initialize();
        }

        /// <summary>
        /// Initializes a new instance of the ServerConnectionCollection class
        /// </summary>
        /// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown</param>
        /// <param name="context">The StreamingContext that contains contextual information about the source or destination</param>
        protected ServerConnectionCollection(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }


        /// <summary>
        /// Adds a connection to the list of available servers
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or IP address of the server</param>
        /// <param name="port">Optiona. The tcp port number of the remote host.</param>
        /// <param name="protocol">Optional. The Memcached protocol to use for the new server connection.</param>
        /// <param name="transport">Optional. The transport protocol to use for the new server connection.</param>
        public void Add(string hostNameOrAddress, int port = 11211, ConnectionType? protocol = null, TransportType? transport = null)
        {
            this.AddServer(hostNameOrAddress, port, protocol, transport);
        }

        /// <summary>
        /// Implements the ISerializable interface and returns the data needed to serialize the ServerConnectionCollection instance
        /// </summary>
        /// <param name="info">A SerializationInfo object that contains the information required to serialize the ServerConnectionCollection instance</param>
        /// <param name="context">A StreamingContext structure that contains the source and destination of the serialized stream associated with the ServerConnectionCollection instance</param>
        [SecurityPermission(SecurityAction.InheritanceDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
        }

        //Internal
        internal ServerConnection AddServer(string hostNameOrAddress, int port = 11211, ConnectionType? protocol = null, TransportType? transport = null)
        {
            if (port < 1 || port > 65535)
            {
                throw new ArgumentException(StringTable.Get("error_InvalidPort"));
            }

            ServerConnection connection = null;
            try
            {
                IPAddress address = IPAddress.None;
                //Check for an IP address string
                if (!IPAddress.TryParse(hostNameOrAddress, out address))
                {
                    //Can't be parsed, try a DNS lookup
                    IPHostEntry hostEntry = Dns.GetHostEntry(hostNameOrAddress);
                    if (hostEntry.AddressList != null && hostEntry.AddressList.Length > 0)
                    {
                        address = hostEntry.AddressList[0];
                    }
                }
                if (address != null)
                {
                    //Create the end point from the address
                    IPEndPoint remoteEp = new IPEndPoint(address, port);
                    //Create the new connection
                    connection = null;

                    ConnectionType conType = protocol ?? this.DefaultConnectionType;
                    TransportType transType = transport ?? this.DefaultTransportType;

                    if (conType == ConnectionType.Binary)
                    {
                        connection = new BinaryConnection(transType, remoteEp);
                    }
                    else
                    {
                        connection = new TextConnection(remoteEp);
                    }
                    //Add it to the list
                    this.Add(connection);
                }
            }
            catch (SocketException)
            {
                throw;
            }

            return connection;
        }

        //Private methods

        private void Initialize()
        {
            this.DefaultConnectionType = ConnectionType.Text;
            this.DefaultTransportType = TransportType.Tcp;
            this._syncRoot = new object();
        }
    }
}
